<?php
/**
 ***********************************************************************************************************************
 * 文件方式记录日志
 ***********************************************************************************************************************
 */

namespace artisan\logger\storage;

/**
 * Class file
 * @package artisan\logger\storage
 */
class file
{
    /**
     * @var string
     */
    public $logFile;

    /**
     * @var bool
     */
    public $enableRotation = true;

    /**
     * @var int
     */
    public $maxFileSize = 10240;

    /**
     * @var int
     */
    public $maxLogFiles = 5;

    /**
     * @var
     */
    public $fileMode;

    /**
     * @var
     */
    public $messages = [];

    /**
     * 初始化记录日志
     */
    public function __construct()
    {
        if ($this->logFile === null) {
            $this->logFile = APPPATH . (globalConfig('log.file') ?: '/log/error.txt');
        }

        $logPath = dirname($this->logFile);
        if(!is_dir($logPath)) {
            self::createDirectory($logPath);
        }
    }

    /**
     * 创建日志目录
     *
     * @param $path
     * @param int $mode
     * @param bool $recursive
     * @return bool
     * @throws \Exception
     */
    public static function createDirectory($path, $mode = 0775, $recursive = true)
    {
        if (is_dir($path)) {
            return true;
        }
        $parentDir = dirname($path);
        if ($recursive && !is_dir($parentDir)) {
            static::createDirectory($parentDir, $mode, true);
        }
        try {
            $result = mkdir($path, $mode);
            chmod($path, $mode);
        } catch(\Exception $e) {
            throw new \Exception("Failed to create directory '$path': " . $e->getMessage(), $e->getCode(), $e);
        }
        return $result;
    }


    /**
     * 移除日志目录
     *
     * @param $dir
     */
    public static function removeDirectory($dir)
    {
        if (!is_dir($dir)) {
            return;
        }

        if (!($handle = opendir($dir))) {
            return;
        }
        while (($file = readdir($handle)) !== false) {
            if ($file === '.' || $file === '..') {
                continue;
            }
            $path = $dir . DIRECTORY_SEPARATOR . $file;
            if (is_dir($path)) {
                static::removeDirectory($path);
            } else {
                unlink($path);
            }
        }
        closedir($handle);

        if (is_link($dir)) {
            unlink($dir);
        } else {
            rmdir($dir);
        }
    }

    /**
     * 组织日志
     *
     * @param $message
     * @param $level
     */
    public function log($message, $level)
    {
        $time = microtime(true);
        $this->messages[] = [$message, $level, $time];
    }

    /**
     * 格式日志
     *
     * @param $message
     * @return string
     */
    public function formatMessage($message)
    {
        list($text, $level, $timestamp) = $message;
        return date('Y-m-d H:i:s', $timestamp) . " [$level] $text";
    }

    /**
     * 记录日志
     *
     * @throws \Exception
     */
    public function flushLog()
    {
        $text = implode("\n", array_map([$this, 'formatMessage'], $this->messages)) . "\n";
        if (($fp = @fopen($this->logFile, 'a')) === false) {
            throw new \Exception("Unable to append to log file: {$this->logFile}");
        }

        @flock($fp, LOCK_EX);
        if ($this->enableRotation) {
            clearstatcache();
        }
        if ($this->enableRotation && @filesize($this->logFile) > $this->maxFileSize * 1024) {
            $this->rotateFiles();
            @flock($fp, LOCK_UN);
            @fclose($fp);
            @file_put_contents($this->logFile, $text, FILE_APPEND | LOCK_EX);
        } else {
            @fwrite($fp, $text);
            @flock($fp, LOCK_UN);
            @fclose($fp);
        }
        if ($this->fileMode !== null) {
            @chmod($this->logFile, $this->fileMode);
        }
    }

    /**
     * 分拆文件
     */
    protected function rotateFiles()
    {
        $file = $this->logFile;
        for ($i = $this->maxLogFiles; $i >= 0; --$i) {

            $rotateFile = $file . ($i === 0 ? '' : '.' . $i);
            if (is_file($rotateFile)) {
                if ($i === $this->maxLogFiles) {
                    @unlink($rotateFile);
                } else {
                    @copy($rotateFile, $file . '.' . ($i + 1));
                    if ($fp = @fopen($rotateFile, 'a')) {
                        @ftruncate($fp, 0);
                        @fclose($fp);
                    }
                }
            }
        }
    }

    /**
     * @throws \Exception
     */
    public function __destruct ()
    {
        $this->flushLog();
    }
    
    /**
     * @param $log
     * @return bool
     */
    public function write($log)
    {
        $this->log($this->format($log['message']), $log['level']);
        return true;
    }

    /**
     * @param $message
     * @return array|string
     */
    private function format($message)
    {
        if(is_string($message) || is_numeric($message)) {
            return $message;
        }
        if(!is_array($message)) {
            $message = (array)$message;
        }
        return json_encode($message, JSON_UNESCAPED_UNICODE);
    }

}